extern crate pest;
#[macro_use]
extern crate pest_derive;

use pest::Parser;
use std::env;
use std::fs;

#[derive(Parser)]
#[grammar = "lexer.pest"]
pub struct SysYLexer;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        std::process::exit(1);
    }

    let filename = &args[1];
    let unparsed_file = fs::read_to_string(filename).expect("cannot read file");

    let program = SysYLexer::parse(Rule::program, &unparsed_file).unwrap_or_else(|e| panic!("{}", e)).next().unwrap();
    let tokens: Vec<_> = program.into_inner().collect();
    let file_lines: Vec<&str> = unparsed_file.lines().collect();
    let mut error_lines = std::collections::HashSet::new();

    for token in &tokens {
        if token.as_rule() == Rule::ERROR {
            let (line, _) = token.as_span().start_pos().line_col();
            error_lines.insert(line);
        }
    }

    if !error_lines.is_empty() {
        let mut sorted_lines: Vec<_> = error_lines.into_iter().collect();
        sorted_lines.sort();
        for line_num in sorted_lines {
            eprintln!("Error type A at Line {}: {}", line_num, file_lines[line_num - 1]);
        }
    } else {
        for token in tokens {
            if token.as_rule() == Rule::EOI {
                break;
            }
            if token.as_rule() != Rule::WHITESPACE && token.as_rule() != Rule::COMMENT {
                let rule = token.as_rule();
                let (line, _) = token.as_span().start_pos().line_col();
                let text = token.as_str().trim();
                if rule == Rule::INTEGER_CONST {
                    let mut inner_rules = token.clone().into_inner();
                    let inner_most = inner_rules.next().unwrap();
                    let num = match inner_most.as_rule() {
                        Rule::hex_const => i32::from_str_radix(&text[2..], 16),
                        Rule::oct_const => i32::from_str_radix(&text[1..], 8),
                        Rule::dec_const => text.parse(),
                        _ => unreachable!()
                    };
                    match num {
                        Ok(n) => eprintln!("{:?} {} at Line {}.", rule, n, line),
                        Err(_) => {
                            eprintln!("Error on Line {}: Invalid number format", line);
                        }
                    }
                } else {
                    eprintln!("{:?} {} at Line {}.", rule, text, line);
                }
            }
        }
    }
}